﻿using System;
using System.Runtime.InteropServices;

namespace Microsoft.Xna.Framework
{
    /// <summary>
    /// Defines a plane.
    /// </summary>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Plane : IEquatable<Plane>
    {
        #region Public Fields
        public float D;
        public Vector3 Normal;
        #endregion

        #region Constructor
        /// <summary>
        /// Creates a new instance of Plane.
        /// </summary>
        /// <param name="a">One point of a triangle defining the Plane.</param>
        /// <param name="b">One point of a triangle defining the Plane.</param>
        /// <param name="c">One point of a triangle defining the Plane.</param>
        public Plane(Vector3 a, Vector3 b, Vector3 c)
        {
            Vector3 ab = b - a;
            Vector3 ac = c - a;

            Vector3 cross = Vector3.Cross(ab, ac);
            Normal = Vector3.Normalize(cross);
            D = -(Vector3.Dot(cross, a));
        } 
        /// <summary>
        /// Creates a new instance of Plane.
        /// </summary>
        /// <param name="value">Vector4 with X, Y, and Z components defining the normal of the Plane. The W component defines the distance of the Plane along the normal from the origin.</param>
        public Plane(Vector4 value)
            : this(new Vector3(value.X, value.Y, value.Z), value.W)
        {

        }
        /// <summary>
        /// Creates a new instance of Plane.
        /// </summary>
        /// <param name="normal">The normal vector of the Plane.</param>
        /// <param name="d">The Plane's distance along its normal from the origin.</param>
        public Plane(Vector3 normal, float d)
        {
            Normal = normal;
            D = d;
        } 
        /// <summary>
        /// Creates a new instance of Plane.
        /// </summary>
        /// <param name="a">X component of the normal defining the Plane.</param>
        /// <param name="b">Y component of the normal defining the Plane.</param>
        /// <param name="c">Z component of the normal defining the Plane.</param>
        /// <param name="d">Distance of the Plane along its normal from the origin.</param>
        public Plane(float a, float b, float c, float d)
            : this(new Vector3(a, b, c), d)
        {

        } 
        #endregion

        #region Public Methods
        /// <summary>
        /// Calculates the dot product of a specified Vector4 and this Plane.
        /// </summary>
        /// <param name="value">The Vector4 to multiply this Plane by.</param>
        /// <param name="result">The dot product of the specified Vector4 and this Plane.</param>
        public void Dot(ref Vector4 value, out float result)
        {
            result = (((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)) + (this.D * value.W);
        }
        /// <summary>
        /// Calculates the dot product of a specified Vector4 and this Plane.
        /// </summary>
        /// <param name="value">The Vector4 to multiply this Plane by.</param>
        public float Dot(Vector4 value)
        {
            return ((((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)) + (this.D * value.W));
        }
        /// <summary>
        /// Returns the dot product of a specified Vector3 and the Normal vector of this Plane plus the distance (D) value of the Plane.
        /// </summary>
        /// <param name="value">The Vector3 to multiply by.</param>
        /// <param name="result">The resulting value.</param>
        public void DotCoordinate(ref Vector3 value, out float result)
        {
            result = (((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)) + this.D;
        }
        /// <summary>
        /// Returns the dot product of a specified Vector3 and the Normal vector of this Plane plus the distance (D) value of the Plane.
        /// </summary>
        /// <param name="value">The Vector3 to multiply by.</param>
        public float DotCoordinate(Vector3 value)
        {
            return ((((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z)) + this.D);
        }
        /// <summary>
        /// Returns the dot product of a specified Vector3 and the Normal vector of this Plane.
        /// </summary>
        /// <param name="value">The Vector3 to multiply by.</param>
        /// <param name="result">The resulting dot product.</param>
        public void DotNormal(ref Vector3 value, out float result)
        {
            result = ((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z);
        }
        /// <summary>
        /// Returns the dot product of a specified Vector3 and the Normal vector of this Plane.
        /// </summary>
        /// <param name="value">The Vector3 to multiply by.</param>
        public float DotNormal(Vector3 value)
        {
            return (((this.Normal.X * value.X) + (this.Normal.Y * value.Y)) + (this.Normal.Z * value.Z));
        }
        /// <summary>
        /// Determines whether the specified Object is equal to the Plane.
        /// </summary>
        /// <param name="other">The Object to compare with the current value.</param>
        public override bool Equals(object other)
        {
            return (other is Plane) ? this.Equals((Plane)other) : false;
        }
        /// <summary>
        /// Determines whether the specified Plane is equal to the Plane.
        /// </summary>
        /// <param name="other">The Plane to compare with the current Plane.</param>
        public bool Equals(Plane other)
        {
            return ((Normal == other.Normal) && (D == other.D));
        }
        /// <summary>
        /// Gets the hash code for this object.
        /// </summary>
        public override int GetHashCode()
        {
            return Normal.GetHashCode() ^ D.GetHashCode();
        }
        /// <summary>
        /// Checks whether the current Plane intersects a BoundingSphere.
        /// </summary>
        /// <param name="sphere">The BoundingSphere to check for intersection with.</param>
        /// <param name="result">An enumeration indicating whether the Plane intersects the BoundingSphere.</param>
        public void Intersects(ref BoundingSphere sphere, out PlaneIntersectionType result)
        {
            result = Intersects(sphere);
        }
        /// <summary>
        /// Checks whether the current Plane intersects a BoundingSphere.
        /// </summary>
        /// <param name="sphere">The BoundingSphere to check for intersection with.</param>
        public PlaneIntersectionType Intersects(BoundingSphere sphere)
        {
            return sphere.Intersects(this);
        }
        /// <summary>
        /// Checks whether the current Plane intersects a specified BoundingFrustum.
        /// </summary>
        /// <param name="frustum">The BoundingFrustum to check for intersection with.</param>
        public PlaneIntersectionType Intersects(BoundingFrustum frustum)
        {
            return frustum.Intersects(this);
        }
        /// <summary>
        /// Checks whether the current Plane intersects a BoundingBox.
        /// </summary>
        /// <param name="box">The BoundingBox to check for intersection with.</param>
        /// <param name="result">An enumeration indicating whether the Plane intersects the BoundingBox.</param>
        public void Intersects(ref BoundingBox box, out PlaneIntersectionType result)
        {
            result = Intersects(box);
        }
        /// <summary>
        /// Checks whether the current Plane intersects a BoundingBox.
        /// </summary>
        /// <param name="box">The BoundingBox to check for intersection with.</param>
        public PlaneIntersectionType Intersects(BoundingBox box)
        {
            return box.Intersects(this);
        }
        /// <summary>
        /// Changes the coefficients of the Normal vector of a Plane to make it of unit length.
        /// </summary>
        /// <param name="value">The plane to normalize.</param>
        /// <param name="result">An existing plane Plane filled in with a normalized version of the specified plane.</param>
        public static void Normalize(ref Plane value, out Plane result)
        {
            float magnitude = 1.0f / (float)Math.Sqrt((value.Normal.X * value.Normal.X) + (value.Normal.Y * value.Normal.Y) + (value.Normal.Z * value.Normal.Z));
            result = new Plane(value.Normal.X * magnitude, value.Normal.Y * magnitude, value.Normal.Z * magnitude, value.D * magnitude); 
        }
        /// <summary>
        /// Changes the coefficients of the Normal vector of a Plane to make it of unit length.
        /// </summary>
        /// <param name="value">The plane to normalize.</param>
        public static Plane Normalize(Plane value)
        {
            float magnitude = 1.0f / (float)Math.Sqrt((value.Normal.X * value.Normal.X) + (value.Normal.Y * value.Normal.Y) + (value.Normal.Z * value.Normal.Z)); 
  
            return new Plane(value.Normal.X * magnitude, value.Normal.Y * magnitude, value.Normal.Z * magnitude, value.D * magnitude ); 
        }
        /// <summary>
        /// Changes the coefficients of the Normal vector of this Plane to make it of unit length.
        /// </summary>
        public void Normalize()
        {
            float magnitude = 1.0f / (float)Math.Sqrt((Normal.X * Normal.X) + (Normal.Y * Normal.Y) + (Normal.Z * Normal.Z)); 
  
            Normal.X *= magnitude; 
            Normal.Y *= magnitude; 
            Normal.Z *= magnitude; 
            D *= magnitude; 
        }
        /// <summary>
        /// Returns a String that represents the current Plane.
        /// </summary>
        public override string ToString()
        {
            return string.Format("{{Normal:{0} D:{1}}}", Normal, D);
        }
        /// <summary>
        /// Transforms a normalized Plane by a Quaternion rotation.
        /// </summary>
        /// <param name="plane">The normalized Plane to transform. This Plane must already be normalized, so that its Normal vector is of unit length, before this method is called.</param>
        /// <param name="rotation">The Quaternion rotation to apply to the Plane.</param>
        /// <param name="result">An existing Plane filled in with the results of applying the rotation.</param>
        public static void Transform(ref Plane plane, ref Quaternion rotation, out Plane result)
        {
            float x2 = rotation.X + rotation.X;
            float y2 = rotation.Y + rotation.Y;
            float z2 = rotation.Z + rotation.Z;
            float wx = rotation.W * x2;
            float wy = rotation.W * y2;
            float wz = rotation.W * z2;
            float xx = rotation.X * x2;
            float xy = rotation.X * y2;
            float xz = rotation.X * z2;
            float yy = rotation.Y * y2;
            float yz = rotation.Y * z2;
            float zz = rotation.Z * z2;

            float x = plane.Normal.X;
            float y = plane.Normal.Y;
            float z = plane.Normal.Z;

            Plane r;
            r.Normal.X = ((x * ((1.0f - yy) - zz)) + (y * (xy - wz))) + (z * (xz + wy));
            r.Normal.Y = ((x * (xy + wz)) + (y * ((1.0f - xx) - zz))) + (z * (yz - wx));
            r.Normal.Z = ((x * (xz - wy)) + (y * (yz + wx))) + (z * ((1.0f - xx) - yy));
            r.D = plane.D;

            result = r; 
        }
        /// <summary>
        /// Transforms a normalized Plane by a Quaternion rotation.
        /// </summary>
        /// <param name="plane">The normalized Plane to transform. This Plane must already be normalized, so that its Normal vector is of unit length, before this method is called.</param>
        /// <param name="rotation">The Quaternion rotation to apply to the Plane.</param>
        public static Plane Transform(Plane plane, Quaternion rotation)
        {
            Plane result;
            float x2 = rotation.X + rotation.X;
            float y2 = rotation.Y + rotation.Y;
            float z2 = rotation.Z + rotation.Z;
            float wx = rotation.W * x2;
            float wy = rotation.W * y2;
            float wz = rotation.W * z2;
            float xx = rotation.X * x2;
            float xy = rotation.X * y2;
            float xz = rotation.X * z2;
            float yy = rotation.Y * y2;
            float yz = rotation.Y * z2;
            float zz = rotation.Z * z2;

            float x = plane.Normal.X;
            float y = plane.Normal.Y;
            float z = plane.Normal.Z;

            result.Normal.X = ((x * ((1.0f - yy) - zz)) + (y * (xy - wz))) + (z * (xz + wy));
            result.Normal.Y = ((x * (xy + wz)) + (y * ((1.0f - xx) - zz))) + (z * (yz - wx));
            result.Normal.Z = ((x * (xz - wy)) + (y * (yz + wx))) + (z * ((1.0f - xx) - yy));
            result.D = plane.D;
            return result; 
        }
        /// <summary>
        /// Transforms a normalized Plane by a Matrix.
        /// </summary>
        /// <param name="plane">The normalized Plane to transform. This Plane must already be normalized, so that its Normal vector is of unit length, before this method is called.</param>
        /// <param name="matrix">The transform Matrix to apply to the Plane.</param>
        /// <param name="result">An existing Plane filled in with the results of applying the transform.</param>
        public static void Transform(ref Plane plane, ref Matrix matrix, out Plane result)
        {
            float x = plane.Normal.X; 
            float y = plane.Normal.Y; 
            float z = plane.Normal.Z; 
            float d = plane.D; 
  
            Matrix transformation = Matrix.Invert(matrix); 
  
            Plane r; 
            r.Normal.X = (((x * transformation.M11) + (y * transformation.M12)) + (z * transformation.M13)) + (d * transformation.M14); 
            r.Normal.Y = (((x * transformation.M21) + (y * transformation.M22)) + (z * transformation.M23)) + (d * transformation.M24); 
            r.Normal.Z = (((x * transformation.M31) + (y * transformation.M32)) + (z * transformation.M33)) + (d * transformation.M34); 
            r.D = (((x * transformation.M41) + (y * transformation.M42)) + (z * transformation.M43)) + (d * transformation.M44); 
            
            result = r; 
        } 
        /// <summary>
        /// Transforms a normalized Plane by a Matrix.
        /// </summary>
        /// <param name="plane">The normalized Plane to transform. This Plane must already be normalized, so that its Normal vector is of unit length, before this method is called.</param>
        /// <param name="matrix">The transform Matrix to apply to the Plane.</param>
        public static Plane Transform(Plane plane, Matrix matrix)
        {
            Plane result;
            float x = plane.Normal.X;
            float y = plane.Normal.Y;
            float z = plane.Normal.Z;
            float d = plane.D;

            Matrix transformation = Matrix.Invert(matrix);
            result.Normal.X = (((x * transformation.M11) + (y * transformation.M12)) + (z * transformation.M13)) + (d * transformation.M14);
            result.Normal.Y = (((x * transformation.M21) + (y * transformation.M22)) + (z * transformation.M23)) + (d * transformation.M24);
            result.Normal.Z = (((x * transformation.M31) + (y * transformation.M32)) + (z * transformation.M33)) + (d * transformation.M34);
            result.D = (((x * transformation.M41) + (y * transformation.M42)) + (z * transformation.M43)) + (d * transformation.M44);

            return result; 
        } 
        #endregion

        #region Operators
        /// <summary>
        /// Determines whether two instances of Plane are not equal.
        /// </summary>
        /// <param name="plane1">The object to the left of the inequality operator.</param>
        /// <param name="plane2">The object to the right of the inequality operator.</param>
        public static bool operator !=(Plane plane1, Plane plane2)
        {
            return !plane1.Equals(plane2);
        }
        /// <summary>
        /// Determines whether two instances of Plane are equal.
        /// </summary>
        /// <param name="plane1">The object to the left of the equality operator.</param>
        /// <param name="plane2">The object to the right of the equality operator.</param>
        public static bool operator ==(Plane plane1, Plane plane2)
        {
            return plane1.Equals(plane2);
        } 
        #endregion
    }
}
